package run.microservice.stock.utils;

import lombok.extern.slf4j.Slf4j;

import java.net.NetworkInterface;
import java.util.Enumeration;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;

/**
 * ID生成器。此ID生成器是Snowflake算法改良版的Base32实现，把时间戳调整为48位，ID生成的时间范围比原雪花算法更大，并且ID进行Base32编码，最终生成的ID是才15位。在单节点中，它肯定是单调递增的。
 *
 * @author lunfy
 * @description:
 * @date 2024-11-17 17:32
 *
 * 节点数 - 时间戳 - 随机数
 * 0000000000-000000000000000000000000000000000000000000000000-000000000000000
 * 节点ID（10位二进制） 占2个字符（base32） =10/5
 * 时间戳（48位二进制） 占10个字符（base32）=48/5
 * 序列号（15位二进制） 占3个字符（base32）=15/5
 *
 * ID长度计算方式：因为基础字符有32个=2的5次方。所以编码后的ID长度 = 二进制位数/5
 * 比如节点ID：10位二进制最大值 = 1024 = 32的平方 = 2的5次方；所以编码后字符长度 = 10/5 = 2
 *
 * 序列号15位二进制，支持每个节点每毫秒产生32768个id序号，完全不用担心ID超前消费的问题
 */
@Slf4j
public class SnowflakeBase32Worker {

    private SnowflakeBase32Worker() {}

    private static class SingletonHolder {
        private static final SnowflakeBase32Worker INSTANCE = new SnowflakeBase32Worker();
    }
    public static SnowflakeBase32Worker getInstance() {
        return SnowflakeBase32Worker.SingletonHolder.INSTANCE;
    }
    /** 防止序列化破坏单例 */
    protected Object readResolve() {
        return SnowflakeBase32Worker.SingletonHolder.INSTANCE;
    }

    // Crockford's Base32 alphabet without padding character
    private static final char[] ENCODING_CHARS = {
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K',
            'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X',
            'Y', 'Z'
    };

    /**
     * The number of bits occupied by workerId
     */
    private final int workerIdBits = 10;

    /**
     * The number of bits occupied by timestamp
     */
    private final int timestampBits = 48;

    /**
     * The default number of bits occupied by sequence
     */
    private final int sequenceBits = 15;

    /**
     * mask that help to extract timestamp and sequence from a long
     */
    private final long timestampAndSequenceMask = ~(-1L << (timestampBits + sequenceBits));

    /**
     * Maximum supported machine id, the result is 1023
     */
    private final int maxWorkerId = ~(-1 << workerIdBits);

    /**
     * business meaning: machine ID (0 ~ 1023)
     */
    private long workerId;

    /**
     * workerId encode to base32
     */
    private String encodeWorkerId;

    /**
     * timestamp and sequence mix in one Long
     * highest 10 bit: not used
     * middle  48 bit: timestamp
     * lowest  15 bit: sequence
     */
    private AtomicLong timestampAndSequence;

    /**
     * instantiate an SnowflakeImproveWorker using given workerId
     * @param workerId if null, then will auto assign one
     */
    public SnowflakeBase32Worker(Long workerId) {
        initTimestampAndSequence();
        initWorkerId(workerId);
    }

    /**
     * get next UUID(base on snowflake algorithm), which look like:
     * highest 10 bit: workerId
     * next   48 bit: timestamp
     * lowest 15 bit: sequence
     * @return UUID
     */
    public String nextId() {
        waitIfNecessary();
        long next = timestampAndSequence.incrementAndGet();
        long timestampWithSequence = next & timestampAndSequenceMask;
        String sequenceStr =encodeToCrockfordBase32(timestampWithSequence, 13);
        return encodeWorkerId + sequenceStr;
    }

    /**
     * init first timestamp and sequence immediately
     */
    private void initTimestampAndSequence() {
        long timestamp = System.currentTimeMillis();
        long timestampWithSequence = timestamp << sequenceBits;
        this.timestampAndSequence = new AtomicLong(timestampWithSequence);
    }

    /**
     * init workerId
     * @param workerId if null, then auto generate one
     */
    private void initWorkerId(Long workerId) {
        if (workerId == null) {
            workerId = generateWorkerId();
        }
        if (workerId > maxWorkerId || workerId < 0) {
            String message = String.format("worker Id can't be greater than %d or less than 0", maxWorkerId);
            throw new IllegalArgumentException(message);
        }
        this.encodeWorkerId = this.encodeToCrockfordBase32(workerId, 2);
    }

    /**
     * block current thread if the QPS of acquiring UUID is too high
     * that current sequence space is exhausted
     */
    private void waitIfNecessary() {
        long currentWithSequence = timestampAndSequence.get();
        long current = currentWithSequence >>> sequenceBits;
        long newest = System.currentTimeMillis();
        if (current >= newest) {
            try {
                Thread.sleep(5);
            } catch (InterruptedException ignore) {
                // don't care
            }
        }
    }

    /**
     * auto generate workerId, try using mac first, if failed, then randomly generate one
     * @return workerId
     */
    private long generateWorkerId() {
        try {
            return generateWorkerIdBaseOnMac();
        } catch (Exception e) {
            return generateRandomWorkerId();
        }
    }

    /**
     * use lowest 10 bit of available MAC as workerId
     * @return workerId
     * @throws Exception when there is no available mac found
     */
    private long generateWorkerIdBaseOnMac() throws Exception {
        Enumeration<NetworkInterface> all = NetworkInterface.getNetworkInterfaces();
        while (all.hasMoreElements()) {
            NetworkInterface networkInterface = all.nextElement();
            boolean isLoopback = networkInterface.isLoopback();
            boolean isVirtual = networkInterface.isVirtual();
            byte[] mac = networkInterface.getHardwareAddress();
            if (isLoopback || isVirtual || mac == null) {
                continue;
            }

            // 使用 mac[4] 的前5位和 mac[5] 的前6位进行计算；最终结果值最大为 1023
            return ((mac[4] & 0B11) << 8) | (mac[5] & 0xFF);
        }
        throw new RuntimeException("no available mac found");
    }

    /**
     * randomly generate one as workerId
     * @return workerId
     */
    private long generateRandomWorkerId() {
        return new Random().nextInt(maxWorkerId + 1);
    }

    /**
     * 将十进制值转换为 Crockford's Base32 字符串。
     * @param value 十进制值
     * @param count 循环次数，即转换后的字符长度
     * @return Crockford's Base32 字符串
     */
    private static String encodeToCrockfordBase32(long value, int count) {
        StringBuilder builder = new StringBuilder();
        for (int i = (count - 1) * 5; i >= 0; i -= 5) {
            int index = (int) ((value >>> i) & 0x1F);
            builder.append(ENCODING_CHARS[index]);
        }
        return builder.toString();
    }

    public static void main(String[] args) {
        SnowflakeBase32Worker idWorker = new SnowflakeBase32Worker(null);
        for (int i = 0; i < 32769; i++) {
            String id = idWorker.nextId();

            if (i == 0) {
                log.info("第一个ID：{}", id);
            }
            if (i == 32766) {
                log.info("最后一个序列号ID：{}", id);
            }
            if (i == 32768) {
                log.info("最后一个ID：{}", id);
            }
        }
    }

}
